﻿using System;
using FluentNHibernate.Cfg;
using FluentNHibernate.Cfg.Db;
using NHibernate;
using NHibernate.ByteCode.Castle;
using NHibernate.Context;

namespace EfficientlyLazy.PhotoFramer.Repository
{
    public sealed class SessionManager
    {
        private ISessionFactory _sessionFactory;
        private bool _isDisposed;
        private static readonly object LockForThreadSafety = new object();

        private SessionManager()
        {
            InitializeSessionFactory();
        }

        public static SessionManager Instance
        {
            get
            {
                lock (LockForThreadSafety)
                {
                    return Nested.SessionManager;
                }
            }
        }

        private class Nested
        {
            private Nested()
            {
            }

            internal static readonly SessionManager SessionManager = new SessionManager();
        }

        public static ISession GetCurrentSession()
        {
            return Instance.GetSession();
        }

        private void BindSession()
        {
            var session = _sessionFactory.OpenSession();
            session.FlushMode = FlushMode.Never;

            CurrentSessionContext.Bind(session);
        }

        private ISession GetSession()
        {
            if (!CurrentSessionContext.HasBind(_sessionFactory))
            {
                BindSession();
            }

            return _sessionFactory.GetCurrentSession();
        }

        public static void FlushCurrentSession()
        {
            Instance.FlushSession();
        }

        private void FlushSession()
        {
            if (CurrentSessionContext.HasBind(_sessionFactory))
            {
                _sessionFactory.GetCurrentSession().Flush();
            }
        }

        public static void Evict(object entity)
        {
            Instance.EvictFromSession(entity);
        }

        private void EvictFromSession(object entity)
        {
            if (CurrentSessionContext.HasBind(_sessionFactory))
            {
                _sessionFactory.GetCurrentSession().Evict(entity);
            }
        }

        public static void RecycleCurrentSession()
        {
            Instance.RecycleSession();
        }

        private void RecycleSession()
        {
            UnbindSession();
            BindSession();
        }

        public static void CloseCurrentSession()
        {
            Instance.UnbindSession();
        }

        private void UnbindSession()
        {
            if (!CurrentSessionContext.HasBind(_sessionFactory))
            {
                return;
            }

            var session = CurrentSessionContext.Unbind(_sessionFactory);

            if (session != null && session.IsOpen)
            {
                session.Dispose();
            }
        }

        private void Dispose(bool disposing)
        {
            if (_isDisposed)
            {
                return;
            }

            lock (LockForThreadSafety)
            {
                if (disposing)
                {
                    UnbindSession();
                    _sessionFactory.Dispose();
                }
            }

            _isDisposed = true;
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        ~SessionManager()
        {
            Dispose(false);
        }

        private void InitializeSessionFactory()
        {
            // NHibernate.Context.ManagedWebSessionContext = managed_web
            // NHibernate.Context.CallSessionContext = call
            // NHibernate.Context.ThreadStaticSessionContext = thread_static
            // NHibernate.Context.WebSessionContext = web

            var fluentconfig = Fluently.Configure()
                .ProxyFactoryFactory(typeof (ProxyFactoryFactory))
                .Database(SQLiteConfiguration.Standard.ConnectionString(Loader.ConnectionString))
                .ExposeConfiguration(cfg => cfg.SetProperty("current_session_context_class", "thread_static"))
                .Mappings(x => x.FluentMappings.AddFromAssemblyOf<SessionManager>().Conventions.AddFromAssemblyOf<SessionManager>())
                .BuildConfiguration();

            _sessionFactory = fluentconfig.BuildSessionFactory();
        }
    }
}